/*
#
#  Copyright 2011 Matthew Jannace
#
#  This file is part of RoboCode BlackHoleAgent
#
#  RoboCode BlackHoleAgent is free software: you can redistribute it and/or
#  modify it under the terms of the GNU General Public License as
#  published by the Free Software Foundation, either version 3 of the
#  License, or (at your option) any later version.
#
#  RoboCode BlackHoleAgent is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
#  General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with RoboCode BlackHoleAgent. If not, see
#  <http://www.gnu.org/licenses/>.
#
 */
package com.jannaceltd.TeamBlackHoleBeer.Modules.Communication;

import com.jannaceltd.TeamBlackHoleBeer.BlackHole_Abstract;
import com.jannaceltd.TeamBlackHoleBeer.BlackHole_Interface;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.Enemy;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.Robots;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.TeamMate;
import com.jannaceltd.TeamBlackHoleBeer.Modules.HelperClasses.VirtualBullets;
import com.jannaceltd.TeamBlackHoleBeer.Utilities.MathUtils;
import java.awt.geom.Point2D;
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
import robocode.MessageEvent;
import robocode.util.Utils;

/**
 * Utility methods to Provides Access to Teammate Communication
 * @author Matthew Jannace <mjannace@gmail.com>
 * @version 1.0
 */
public class Communication implements BlackHole_Interface {

    private BlackHole_Abstract Agent = null;
    /**
     * Message to contains information that is shared with teammates
     * via broadcast message
     */
    protected TeamMateMessage msg = null;

    /**
     * Creates a new Communication Object
     * @param Agent
     */
    public Communication(BlackHole_Abstract Agent) {
        this.Agent = Agent;
        this.msg = new TeamMateMessage();
    }

    /**
     * Sends a broadcasts message to teammates. The message contains a TeamMateMessage Object.
     * @serialData TeamMateMessage Object
     */
    public void doBroadcastUpdate() {
        msg.setTeamatesNextLocation(getMyNextLocation().getName(), getMyNextLocation());
        msg.setTeamates(Agent.getMyInfo().getName(), Agent.getMyInfo());
        msg.setTarget(Agent.target.getName());

        try {
            Agent.broadcastMessage(msg);
            msg = null;
            msg = new TeamMateMessage();
        } catch (Exception ex) {
            Logger.getLogger(Communication.class.getName()).log(Level.SEVERE, null, ex);
            System.out.println("Error Broadcasting Message, Exception = " + ex.toString());
        }
    }

    /**
     * Adds enemy location entry to TeamMateMessage Object to be broadcasted.
     * @param en
     * @see #doBroadcastUpdate()
     */
    public void doBroadcastEnemyLocation(Enemy en) {
        msg.setEnemies(en.getName(), en);
    }

    /**
     * Adds virtual bullet entry to TeamMateMessage Object to be broadcasted.
     * @param vb
     * @param state
     * @see #doBroadcastUpdate()
     */
    public void doBroadcastBullet(VirtualBullets vb, FriendlyFireState state) {
        msg.setFriendlyFire(vb.getName(), vb, state);
    }

    /**
     * Processes MessageEvent object by extracting the TeamMateMessage object
     * and processing the TeamMateMessage fields
     * @param event
     * @see #doBroadcastUpdate()
     */
    public void doProcessReceivedMessages(Vector<MessageEvent> msgEvents) {
        try {
            for (MessageEvent event : msgEvents) {
                Object recievedMsg = event.getMessage();
                if (recievedMsg instanceof TeamMateMessage) {

                    TeamMateMessage teammsg = (TeamMateMessage) recievedMsg;
                    updateTeam(teammsg);
                    updateTeamMateNextLocation(teammsg);
                    updateFriendlyFire(teammsg);
                    updateEnemy(teammsg);

                } else {
                    System.out.println("Trying to Process message in Unknown Message Format!!\n Msg Class = " + recievedMsg.toString());
                    return;
                }
            }
        } catch (Exception ex) {
            System.out.println("Error Processeing Recieved Message, Exception = " + ex.toString());
        }
    }

    /**
     * Processes MessageEvent object by extracting the TeamMateMessage object
     * and processing the TeamMateMessage fields
     * @param event
     * @see #doBroadcastUpdate()
     */
    public void doOnMessageReceived(MessageEvent event) {
        try {
            Object recievedMsg = event.getMessage();
            if (recievedMsg instanceof TeamMateMessage) {

                TeamMateMessage teammsg = (TeamMateMessage) recievedMsg;
                updateTeam(teammsg);
                updateTeamMateNextLocation(teammsg);
                updateFriendlyFire(teammsg);
                updateEnemy(teammsg);
            } else {
                System.out.println("Trying to Process message in Unknown Message Format!!\n Msg Class = " + recievedMsg.toString());
                return;
            }
        } catch (Exception ex) {
            System.out.println("Error Processeing Recieved Message, Exception = " + ex.toString());
        }
    }

    /**
     * Updates the teammates data structure by updating status information for
     * the agent that sent the message by processing the TeamMateMessage fields
     * @param teammsg
     * @see #doOnMessageReceived(robocode.MessageEvent)
     */
    protected void updateTeam(TeamMateMessage teammsg) {
        Robots bot;
        for (TeamMate tm : teammsg.getTeamates().values()) {
            bot = Agent.teamates.get(tm.getName());
            if (bot == null) {
                bot = new TeamMate(Agent);
                Agent.teamates.put(tm.getName(), (TeamMate) bot);
            }
            updateBot(bot, tm);
        }
    }

    /**
     * Updates the teammatesNextLocation data structure by updating
     * teammatesNextLocation status information for the agent that sent the
     * message by processing the TeamMateMessage fields
     * @param teammsg
     * @see #doOnMessageReceived(robocode.MessageEvent)
     */
    protected void updateTeamMateNextLocation(TeamMateMessage teammsg) {
        Robots bot;
        for (TeamMate tm : teammsg.getTeamatesNextLocation().values()) {
            bot = Agent.teamatesNextLocation.get(tm.getName());
            if (bot == null) {
                bot = new TeamMate(Agent);
                Agent.teamatesNextLocation.put(tm.getName(), (TeamMate) bot);
            }
            updateBot(bot, tm);
        }
    }

    /**
     * Updates the friendlyFire data structure by updating
     * friendlyFire status information for the agent that sent the
     * message by processing the TeamMateMessage fields
     * @param teammsg
     * @see #doOnMessageReceived(robocode.MessageEvent)
     */
    protected void updateFriendlyFire(TeamMateMessage teammsg) {

        VirtualBullets vb;
        for (VirtualBullets tmVB : teammsg.getFriendlyFire(FriendlyFireState.ACTIVE).values()) {
            vb = Agent.friendlyFire.get(tmVB.getName());
            if (vb == null) {
                vb = new VirtualBullets();
                Agent.friendlyFire.put(tmVB.getName(), vb);
            }
            vb.setName(tmVB.getName());
            vb.setSourceLocation(tmVB.getSourceLocation());
            vb.setFireTime(tmVB.getFireTime());
            vb.setPower(tmVB.getPower());
            vb.setHeadingRadians(tmVB.getHeadingRadians());
            vb.setVelocity(tmVB.getVelocity());

        }

        // Update the InActive Friendly Fire
        for (VirtualBullets tmVB : teammsg.getFriendlyFire(FriendlyFireState.INACTIVE).values()) {

            vb = Agent.friendlyFire.get(tmVB.getName());
            if (vb != null) {
                Agent.friendlyFire.remove(tmVB.getName());
            }
        }
    }

    /**
     * Updates the enemies data structure by updating
     * enemies status information gathered by the agent that sent the
     * message by processing the TeamMateMessage fields
     * @param teammsg
     * @see #doOnMessageReceived(robocode.MessageEvent)
     */
    protected void updateEnemy(TeamMateMessage teammsg) {
        Robots bot;
        for (Enemy en : teammsg.getEnemies().values()) {
            bot = Agent.enemies.get(en.getName());
            if (bot == null) {
                bot = new Enemy();
                Agent.enemies.put(en.getName(), (Enemy) bot);
            }
            updateBot(bot, en);
        }
    }

    /**
     * Updates the a generic Robot object in which Enemies and Teammates data
     * structures Extend. A robot object from the TeamMateMessage is used to
     * update the generic Robot pointing to a Teammate of Enemies Data structure.
     * @param agentBot
     * @param msgBot 
     * @see #doOnMessageReceived(robocode.MessageEvent)
     */
    protected void updateBot(Robots agentBot, Robots msgBot) {

        agentBot.setName(msgBot.getName());
        agentBot.setEnergy(msgBot.getEnergy());
        agentBot.setAlive(msgBot.getAlive());
        double CalcAngl = MathUtils.calcAngle(msgBot.getPos(), Agent.getPos());
        agentBot.setBearingRadians(Utils.normalRelativeAngle(CalcAngl - Agent.getHeadingRadians()));
        agentBot.setHeadingRadians(msgBot.getHeadingRadians());
        agentBot.setVelocity(msgBot.getVelocity());
        agentBot.setDistance(Agent.getPos().distance(msgBot.getPos()));
        agentBot.setPos(msgBot.getPos());
        agentBot.setChangehead(msgBot.getChangehead());
        agentBot.setCtime(msgBot.getCtime());
    }

    /**
     * Returns a TeamMate Object representing the next location of the Agent
     * @return TeamMate
     */
    protected TeamMate getMyNextLocation() {
        return new TeamMate(
                /* Name = */Agent.getName(),
                /* Energy = */ Agent.getEnergy(),
                /* Alive = */ true,
                /* Position = */ new Point2D.Double(Agent.move.getNextDestination().getX(), Agent.move.getNextDestination().getY()),
                /* Bearing = */ 0.0,
                /* Heading = */ Agent.getHeadingRadians(),
                /* Velocity = */ Agent.getVelocity(),
                /* Distance = */ 0.0,
                /* ChangeHeading = */ 0.0,
                /* Velocity = */ Agent.getTime());
    }
}
